package com.plugin.seed.mybatis.defaultvalue.util;

import com.plugin.seed.mybatis.defaultvalue.annotation.NumberColumnDefaultValue;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.io.Resources;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import static org.springframework.util.StringUtils.tokenizeToStringArray;

/**
 * @author Peter Fan
 */
@Slf4j
public class DefaultValueUtil {

    private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();

    private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory();

    private static Map<Class, Map<Field, Object>> fieldDefaultValueMap = new ConcurrentHashMap<>();

    public static void flushFieldDefaultValueMap(String typeAliasesPackage) {
        Set<Class<?>> classes = scanClasses(typeAliasesPackage);
        doFlushFieldDefaultValueMap(classes);
        log.info("fieldDefaultValueMap size ", fieldDefaultValueMap.size());
    }

    private static Set<Class<?>> scanClasses(String packagePatterns) {
        Set<Class<?>> classes = new HashSet<>();
        String[] packagePatternArray = tokenizeToStringArray(packagePatterns,
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        try {
        for (String packagePattern : packagePatternArray) {
            Resource[] resources = RESOURCE_PATTERN_RESOLVER.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                    + ClassUtils.convertClassNameToResourcePath(packagePattern) + "/**/*.class");
            for (Resource resource : resources) {

                    ClassMetadata classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource).getClassMetadata();
                    Class<?> clazz = Resources.classForName(classMetadata.getClassName());
                    classes.add(clazz);

            }
        }
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return classes;
    }

    private static void doFlushFieldDefaultValueMap(Set<Class<?>> classes) {
        if (classes.isEmpty()) {
            return;
        }

        classes.stream().forEach(item -> {
            Map<Field, Object> defaultValueMap = getClassDefaultValueMap(item);
            fieldDefaultValueMap.put(item, defaultValueMap);
        });
    }

    private static Map<Field, Object> getClassDefaultValueMap(Class clazz) {
        Map<Field, Object> defaultValueMap = new HashMap<>();
        Field[] fields = clazz.getDeclaredFields();
        try {
            for (Field field : fields) {
                if (Number.class.isAssignableFrom(field.getType())) {
                    NumberColumnDefaultValue numberColumnDefaultValue = field.getAnnotation(NumberColumnDefaultValue.class);
                    if (numberColumnDefaultValue != null) {
                        field.setAccessible(true);
                        Constructor constructor = field.getType().getConstructor(String.class);
                        Object defaultValue = constructor.newInstance(String.valueOf(numberColumnDefaultValue.value()));
                        defaultValueMap.put(field, defaultValue);
                    }
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return defaultValueMap;
    }

    public static void initDefaultValue(Object object) {
        if (object == null) {
            return;
        }

        Map<Field, Object> defaultValueMap = getDefaultValueMap(object.getClass());
        defaultValueMap.forEach((key, value) -> {
            try {
                if (key.get(object) == null) {
                    key.set(object, value);
                }
            } catch (IllegalAccessException e) {
                log.error(e.getMessage());
            }
        });
    }

    private static Map<Field, Object> getDefaultValueMap(Class clazz) {
        if (fieldDefaultValueMap.containsKey(clazz)) {
            return fieldDefaultValueMap.get(clazz);
        }

        Map<Field, Object> defaultValueMap = getClassDefaultValueMap(clazz);
        fieldDefaultValueMap.put(clazz, defaultValueMap);
        return defaultValueMap;
    }

    public static void cleanDefaultValue(Object object) {
        Map<Field, Object> defaultValueMap = getDefaultValueMap(object.getClass());
        defaultValueMap.forEach((key, value) -> {
            try {
                if (value.equals(key.get(object))) {
                    key.set(object, null);
                }
            } catch (IllegalAccessException e) {
                log.error(e.getMessage());
            }
        });
    }

}
